package xdu.lz.netty.d_nio_selector.selector;



import java.io.IOException;
import java.net.InetSocketAddress;
import java.net.ServerSocket;

import java.net.Socket;
import java.nio.ByteBuffer;
import java.nio.channels.*;
import java.nio.charset.Charset;
import java.util.*;

/**
 * @author lizhen
 * @date 2019/8/13 22:44
 * @desc 一个channel连接多个客户端，注册到selector，进行消息转发
 */
public class NioServer {

//    把客户端的信息记录在服务器上
    private static  HashMap<String, SocketChannel> clientMap = new HashMap<>();

    public static void main(String[] args) throws Exception{
        //1通过 获取的 channel对象 获取socket，绑定端口
        ServerSocketChannel serverSocketChannel = ServerSocketChannel.open();
        serverSocketChannel.configureBlocking(false);

        ServerSocket serverSocket = serverSocketChannel.socket();
        serverSocket.bind(new InetSocketAddress(8899));

        //2创建selector对象
        Selector selector = Selector.open();
        //3把serverSocketChannel注册到selector上,注册连接事件,并返回一个selectionKey
        serverSocketChannel.register(selector,SelectionKey.OP_ACCEPT);

        //4事件处理
        while( true ){
            //4.1 阻塞监听事件，返回监听到的事件的个数
            System.out.println(0);
            int eventNums = selector.select();
            //4.2获取到selected-key的集合
            System.out.println(1);
            // TODO 思考： 如果是两个客户端同时发生写事件，但是SelectionKey是set集合，后面只能获取一个客户端
            Set<SelectionKey> selectionKeys = selector.selectedKeys();

            //4.3遍历SelectionKey集合，根据事件类型判断并给出相应处理
            selectionKeys.forEach( selectionKey -> {
                //服务器通过SocketChannel 与客户端交互,与IO中Socket对应
                final SocketChannel client;

                try {
                    //5如果是连接事件，可以通过selectionKey获取与之关联的channel对象
                    if( selectionKey.isAcceptable()){

                        //5.1 ServerSocketChannel在建立连接后，任务已经完成，通过它获取与客户端交互的对象SocketChannel
                       //对ServerSocketChannel注册了isAcceptable事件，当该事件发生一定能转为ServerSocketChannel
                        ServerSocketChannel server = (ServerSocketChannel) selectionKey.channel();
                        System.out.println("serverSocketChannel = " + serverSocketChannel);
                        client = server.accept();
                        System.out.println("获取客户端连接："+client);
                        client.configureBlocking(false);

                        //5.2把SocketChannel注册到selector上，注册读监听事件
                        client.register(selector,SelectionKey.OP_READ);

                        //5.3把客户端的信息记录在服务器上
                        String clientKey = "["+UUID.randomUUID().toString()+"]";
                        clientMap.put(clientKey,client);
                        //5.4   如果是迭代器的方法直接remove，遍历的方法在最后清空集合

                     //6 如果是可读数据事件
                    }else if( selectionKey.isReadable()){
                        //6.1对client注册了isReadable事件，如果该事件发生一定可以转为client
                        client = (SocketChannel) selectionKey.channel();
                        System.out.println("client = " + client);
                        ByteBuffer readBuffer = ByteBuffer.allocateDirect(1024);
                        int count = client.read(readBuffer);

                        if( count>0 ){
                            //6.2写到所有的连接的客户端
                            readBuffer.flip();
                            Charset charset = Charset.forName("utf-8");
                            //把读取到的消息解码成CharBuffer 并转成char数组   然后转成String字符串
                            String receivedMsg = String.valueOf(charset.decode(readBuffer).array());
                            System.out.println(client+ ":" + receivedMsg);

                            String sendKey = null;
                            //6.2.1通过当前value--socketchannel找到对应的key
                            for (Map.Entry<String, SocketChannel> entry : clientMap.entrySet()) {
                                if( client == entry.getValue()){
                                    sendKey = entry.getKey();
                                    break;
                                }
                            }
                            //6.2.2给每个客户端发送消息
                            for (Map.Entry<String, SocketChannel> entry : clientMap.entrySet()) {
                                SocketChannel socketChannel = entry.getValue();
                                ByteBuffer writeBuffer = ByteBuffer.allocate(1024);
                                //writebuffer读取数据
                                writeBuffer.put((sendKey+" :"+ receivedMsg).getBytes());
                                writeBuffer.flip();
                                //wirtebuffer写数据
                                socketChannel.write(writeBuffer);
                            }
                        }
                    }
                } catch (IOException e) {
                    e.printStackTrace();
                }

            });
            //清空集合。或者用迭代器的方法，当一个事件发生后，处理完毕记得remove
            selectionKeys.clear();
        }

    }
}
